package com.onpositive.wiki3.db.catmodel;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;

import com.carrotsearch.hppc.IntArrayList;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.onpositive.compactdata.IntToIntArrayMap;
import com.onpositive.wiki3.db.ITitleMapper;
import com.onpositive.wiki3.db.IWikiDB;

public class CompactCats {

	protected IntToIntArrayMap cMap = new IntToIntArrayMap();
	protected IntToIntArrayMap pMap = new IntToIntArrayMap();

	protected ITitleMapper mapper;
	private IWikiDB db;

	public CompactCats(File f, boolean notm, IWikiDB m) throws IOException {
		File file = new File(f.getAbsolutePath() + ".fast");
		this.mapper = m.getTitleMapper();
		this.db = m;
		if (file.exists()) {
			DataInputStream d = new DataInputStream(new BufferedInputStream(
					new FileInputStream(file)));
			cMap.read(d);
			pMap.read(d);
			d.close();
			return;
		}

		DataInputStream d = new DataInputStream(new BufferedInputStream(
				new FileInputStream(f)));
		int lc = -1;
		IntArrayList ss = new IntArrayList();
		IntObjectOpenHashMap<int[]> childrenQMap = new IntObjectOpenHashMap<int[]>(
				10 * 1000 * 1000);
		IntObjectOpenHashMap<IntArrayList> parentsQMap = new IntObjectOpenHashMap<IntArrayList>(
				10 * 1000 * 1000);
		try {

			while (true) {
				if (notm) {
					throw new UnsupportedOperationException();
				} else {
					int readInt = d.readInt();
					// sz++;
					if (readInt < 0) {
						if (lc > 0) {
							int[] mm = ss.toArray();
							childrenQMap.put(lc, mm);
						}
						lc = -readInt;
						ss = new IntArrayList();
						// count++;
					} else {

						IntArrayList intArrayList = parentsQMap.get(readInt);
						if (intArrayList == null) {
							intArrayList = new IntArrayList();
							parentsQMap.put(readInt, intArrayList);
						}
						ss.add(readInt);
						intArrayList.add(lc);
					}
				}
			}
		} catch (EOFException e) {
			int[] mm = ss.toArray();
			childrenQMap.put(lc, mm);
			// System.out.println(sz);
			// System.out.println(rc);
			// System.out.println(count);
			// System.out.println(mm.size());
		}
		IntToIntArrayMap childMap = new IntToIntArrayMap();
		IntToIntArrayMap pMap = new IntToIntArrayMap();
		for (int z : childrenQMap.keys().toArray()) {
			int[] is = childrenQMap.get(z);
			childMap.add(z, is);
		}
		for (int z : parentsQMap.keys().toArray()) {
			int[] is = parentsQMap.get(z).toArray();
			pMap.add(z, is);
		}
		DataOutputStream q = new DataOutputStream(new BufferedOutputStream(
				new FileOutputStream(f.getAbsolutePath() + ".fast")));
		childMap.store(q);
		pMap.store(q);
		q.close();
		this.pMap = pMap;
		this.cMap = childMap;
	}

	public static class DS {
		public DS(int a, int level) {
			this.value = a;
			this.distance = level;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + value;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			DS other = (DS) obj;
			if (value != other.value)
				return false;
			return true;
		}

		public int value;

		public int distance;

	}

	public DS[] getAllParents(int redirect, int level) {
		int[] is = pMap.get(redirect);
		HashSet<DS> r = new HashSet<CompactCats.DS>();
		HashMap<DS, Integer> vm = new HashMap<CompactCats.DS, Integer>();
		if (is != null) {
			for (int a : is) {
				DS e = new DS(a, level);
				vm.put(e, level);
				r.add(e);
				String title = mapper.getTitle(a);
				if (level == -1 && title.contains("disambiguation")) {
					if (db.getContentAccessor().hasText(redirect)) {
						String textContent = db.getContentAccessor()
								.getTextContent(redirect);
						try {
							BufferedReader rs = new BufferedReader(
									new java.io.StringReader(textContent));
							while (true) {
								String readLine = rs.readLine();
								if (readLine == null) {
									break;
								}
								if (readLine.startsWith("* [[")) {
									String string = readLine.substring(4);
									int indexOf = string.indexOf(']');
									if (indexOf != -1) {
										string = string.substring(0, indexOf);
										//System.out.println(">>"+string);
										int[] fuzzyMatches = db
												.getTitleMapper()
												.getAllFuzzyMatches(string);
										for (int q : fuzzyMatches) {
											//System.out.println(db.getTitleMapper().getTitle(q));
											DS[] allParents = getAllParents(q,
													level+1);
											for (DS m : allParents) {
												if (!r.contains(m)) {
													Integer integer = vm.get(m);
													if (integer == null
															|| integer >= m.distance) {
														r.add(m);
													}
												}
											}
										}
									}
								}
							}
						} catch (Exception ex) {
							ex.printStackTrace();
							// TODO: handle exception
						}
					}
				}
				int delta = 1;
				if (title.contains("history_")) {
					delta += 3;
				}
				if (title.contains("_by_")) {
					delta -= 1;
				}
				if (title.contains("_of_")) {
					delta -= 1;
				}
				// System.out.println(mapper.getTitle(e.value)+":"+e.distance);
				vm.put(e, level);
				DS[] allParents = getAllParents(a, level + delta);
				for (DS m : allParents) {
					if (!r.contains(m)) {
						Integer integer = vm.get(m);
						if (integer == null || integer >= m.distance) {
							r.add(m);
						}
					}
				}
			}
		}
		return r.toArray(new DS[r.size()]);
	}

	IntIntOpenHashMap sizes = new IntIntOpenHashMap();

	public long getInnaccurateSize(int r) {
		if (sizes.containsKey(r)) {
			return sizes.get(r);
		}
		int[] is = cMap.get(r);
		int mq = 0;
		for (int m : is) {
			mq++;
			mq += getInnaccurateSize(m);
		}
		sizes.put(r, mq);
		return mq;
	}
	
	

	public int getAccurateSize(int r) {
		IntOpenHashSet ms = new IntOpenHashSet();
		if (sizes.containsKey(r)) {
			return sizes.get(r);
		}
		int[] is = cMap.get(r);
		int mq = 0;
		for (int m : is) {
			if (!ms.contains(m)) {
				ms.add(m);
				mq++;
				mq += getAccurateSize(m, ms);
			}
		}
		sizes.put(r, mq);
		return mq;
	}

	private int getAccurateSize(int r, IntOpenHashSet ms) {
		if (sizes.containsKey(r)) {
			return sizes.get(r);
		}
		int[] is = cMap.get(r);
		int mq = 0;
		for (int m : is) {
			if (!ms.contains(m)) {
				ms.add(m);
				mq++;
				mq += getAccurateSize(m, ms);
			}
		}
		// sizes.put(r, mq);
		return mq;
	}

	public DS[] getAllChildren(int redirect, int level) {

		int[] is = cMap.get(redirect);
		HashSet<DS> r = new HashSet<CompactCats.DS>();
		if (is != null) {
			for (int a : is) {
				r.add(new DS(a, level));
				DS[] allParents = getAllChildren(a, level + 1);
				for (DS m : allParents) {
					r.add(m);
				}
			}
		}
		return r.toArray(new DS[r.size()]);
	}

	public int[] getParents(int redirect) {
		int[] is = pMap.get(redirect);
		return is;
	}
	public int[] getChildren(int redirect) {
		int[] is = cMap.get(redirect);
		return is;
	}
}
